import orjson, os, logging, redis, asyncio,traceback
from dotenv import load_dotenv
load_dotenv()
os.environ.setdefault('CYQUERY_SETTINGS_MODULE', 'app.settings')
from typing import Dict, List,Optional
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from contextlib import asynccontextmanager
from app.models import MessageModel, MessageUserModel
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from cy_query.utils.date_time import DateTime,DateError
from cy_query import MigrateTable,connections
from app.utils import verify_signed_token
from asgiref.sync import sync_to_async
from pydantic import BaseModel

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
TYPE = {'normal':0,'system':1,'todo':2}
# 定义Pydantic模型用于API请求验证
class HealthResponse(BaseModel):
    msg: str
    code: int
    data: Optional[dict] = None

class OnlineResponse(BaseModel):
    msg: str
    code: int
    data: List[int]
    

@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时的操作
    await sync_to_async(create_tables)()

    # 启动定时任务调度器
    scheduler.start()
    logger.info("定时任务调度器已启动")

    # 启动Redis监听任务
    asyncio.create_task(listen_redis())
    logger.info("应用启动完成")

    yield  # 应用运行中

    # 关闭时的操作
    scheduler.shutdown()
    logger.info("定时任务调度器已关闭")

# 初始化FastAPI应用
app = FastAPI(title="WebSocket消息服务",lifespan=lifespan)

# 配置
REDIS_URL = os.getenv("REDIS_URL")
USER_TABLE_FIELD = os.getenv("USER_TABLE_FIELD", "system_users.id")

# 初始化Redis连接
redis_client = redis.from_url(REDIS_URL)


# 检查并创建数据库表
def create_tables():
    logger.info('已获取到DATABASE_ENGINE:%s' % os.environ.get("DATABASE_ENGINE"))
    if os.getenv('DATABASE_ENGINE') is None:
        raise ValueError("请先配置数据库连接")
    settings_dict = {
        'ENGINE': os.getenv('DATABASE_ENGINE'),
        'NAME': os.getenv('DATABASE_NAME'),
        'USER': os.getenv('DATABASE_USER'),
        'PASSWORD': os.getenv('DATABASE_PASSWORD'),
        'HOST': os.getenv('DATABASE_HOST'),
        'PORT': os.getenv('DATABASE_PORT'),
        'OPTIONS': os.getenv('DATABASE_OPTIONS', {}),
        'TIME_ZONE': None,
        'TEST': {'CHARSET': None, 'COLLATION': None, 'MIGRATE': True, 'MIRROR': None, 'NAME': None},
        'ATOMIC_REQUESTS': False,
        'AUTOCOMMIT': True,
        'CONN_MAX_AGE': 0,
        'CONN_HEALTH_CHECKS': False
    }
    connections.databases['default'] = settings_dict
    # 判断表是否已经存在,如果已经存在就不创建

    migrate = MigrateTable()
    try:
        MessageModel.objects.first()
    except:
        migrate.create_table(MessageModel,False)
    try:
        MessageUserModel.objects.first()
    except:
        migrate.create_table(MessageUserModel,False)


# 初始化定时任务调度器
scheduler = AsyncIOScheduler()

# 存储在线用户的WebSocket连接
active_connections: Dict[int, WebSocket] = {}


# 处理接收到的消息
async def process_message(message_data: dict):
    create_time = DateTime().to_string()
    try:
        # 获取消息
        try:
            message = await MessageModel.objects.aget(id=message_data['message_id'])
        except MessageModel.DoesNotExist:
            logger.error(f"时间:{create_time},未获取到消息ID: {message_data['message_id']}")
            return
        scheduled_time = message.scheduled_time
        # 解析时间
        if scheduled_time:
            try:
                scheduled_time = DateTime(scheduled_time).to_string('datetime')
            except DateError:
                logger.error(f"时间:{create_time},无效的时间格式: {scheduled_time}")
                return

        # 如果是立即发送（没有定时时间或定时时间已过）
        if not scheduled_time or scheduled_time <= DateTime():
            await send_message_immediately(message)
        else:
            # 定时发送
            scheduler.add_job(
                send_scheduled_message,
                'date',
                run_date=scheduled_time,
                args=[message.id]
            )
            logger.info(f"时间:{create_time},定时消息已安排: {message.id}, 时间: {scheduled_time}")

    except Exception as e:
        logger.error(f"时间:{create_time},处理消息时出错: {e}")
        traceback.print_exc()


# 立即发送消息
async def send_message_immediately(message: MessageModel):
    create_time = DateTime().to_string('datetime')
    try:
        target_creators = await MessageUserModel.objects.filter(message=message).values_list('target_creator',flat=True).tolist()
        # 构建要发送的消息内容
        message_content = {
            "id": message.id,
            "type": message.type,
            "content": message.content,
            "grade": message.grade,
            "creator": message.creator,
            "send_time": create_time,
            'message_options':message.message_options,
            'content_options':message.content_options,
            'options':message.options,
            'title':message.title
        }
        update_user_ids = []
        # 通过WebSocket发送给在线用户
        for target in target_creators:
            if target in active_connections:
                try:
                    await active_connections[target].send_json(message_content)
                    logger.info(f"时间:{create_time},消息 {message.id} 已发送给用户 {target}")
                    update_user_ids.append(target)
                except Exception as e:
                    logger.error(f"时间:{create_time},发送消息给用户 {target} 失败: {e}")
        message.is_success = True
        await message.asave()
        await MessageUserModel.objects.filter(message=message).aupdate(create_time=DateTime().to_string('datetime'))
        if update_user_ids:
            await MessageUserModel.objects.filter(message=message,target_creator__in=update_user_ids).aupdate(is_success=True)
        logger.info(f"时间:{create_time},消息发送成功!")
    except Exception as e:
        logger.error(f"时间:{create_time},立即发送消息时出错: {e}")
        traceback.print_exc()


# 发送定时消息
async def send_scheduled_message(message_id: int):
    now = DateTime()
    try:
        # 获取消息
        instance = await MessageModel.objects.aget(id=message_id)
        await send_message_immediately(instance)
        logger.info(f"时间:{now.to_string()},定时消息 {message_id} 已发送")

    except Exception as e:
        logger.error(f"时间:{now.to_string()},发送定时消息时出错: {e}")


# 监听Redis消息
async def listen_redis():
    pubsub = redis_client.pubsub()
    dtype = os.getenv('PUBSUB_SUBSCRIBE','messages')
    pubsub.subscribe(dtype)

    logger.info("开始监听Redis消息队列")

    while True:
        message = pubsub.get_message(ignore_subscribe_messages=True, timeout=1)
        now = DateTime()
        if message and message["type"] == "message":
            try:
                message_data = orjson.loads(message["data"])
                logger.info(f"时间:{now.to_string()},从Redis接收到消息: {message_data}")
                # 处理消息
                await process_message(message_data)

            except orjson.JSONDecodeError:
                logger.error(f"时间:{now.to_string()},无法解析Redis消息")
            except Exception as e:
                logger.error(f"时间:{now.to_string()},处理Redis消息时出错: {e}")

        await asyncio.sleep(0.1)


# WebSocket端点
@app.websocket("/ws/{user}")
async def websocket_endpoint(websocket: WebSocket, user: str):
    await websocket.accept()
    now = DateTime()
    try:
        data = verify_signed_token(user)
        user_id = data['user_id']
    except:
        logger.error("时间:%s,用户：%s错误，无法解析" % (now.to_string(),user))
        return
    if DateTime(data['create_at']).add_day(1) < now:
        try:
            await websocket.send_json({'title':"超时",'content':"令牌超时，请重新登录",'now':now.to_string()})
            await websocket.close() # 服务器主动断开连接
        except:
            pass
        logger.error("时间:%s,令牌超时!" % now.to_string())
        return

    # 如果同一用户已有连接，关闭旧连接
    if user_id in active_connections:
        try:
            old_connection = active_connections[user_id]
            await old_connection.send_json({
                'title': "断开连接",
                'content': "您的账号在其他设备登录",
                'now': now.to_string()
            })
            await old_connection.close()
            logger.info(f"时间:{now.to_string()},用户 {user_id} 的旧连接已关闭")
        except Exception as e:
            logger.error(f"时间:{now.to_string()},关闭用户 {user_id} 的旧连接时出错: {e}")
    active_connections[user_id] = websocket
    await websocket.send_json({
        'title': "连接成功",
        'content': "WebSocket连接已建立",
        'now': now.to_string()
    })
    logger.info(f"时间:{now.to_string()},用户 {user_id} 已连接")
    try:
        while True:
            # 保持连接，不需要处理客户端发送的消息
            data = await websocket.receive_text()
            logger.info(f"时间:{now.to_string()},从用户 {user_id} 收到消息: {data}")

    except WebSocketDisconnect:
        if user_id in active_connections:
            del active_connections[user_id]
            logger.info(f"时间:{now.to_string()},用户 {user_id} 已断开连接")
    except Exception as e:
        logger.error(f"时间:{now.to_string()},WebSocket连接出错: {e}")
        if user_id in active_connections:
            del active_connections[user_id]


# 健康检查端点
@app.get("/health", response_model=HealthResponse)
async def health_check():
    redis_status = "healthy"
    try:
        redis_client.ping()
    except Exception as e:
        redis_status = f"unhealthy: {str(e)}"
    
    # 检查数据库连接
    db_status = "healthy"
    try:
        await MessageModel.objects.acount()
    except Exception as e:
        db_status = f"unhealthy: {str(e)}"
    
    status = "healthy" if redis_status == "healthy" and db_status == "healthy" else "unhealthy"
    
    return {
        "msg": status,
        "code": 2000 if status == "healthy" else 4000,
        "data": {
            "redis": redis_status,
            "database": db_status,
            "active_connections": len(active_connections)
        }
    }

@app.get('/online', response_model=OnlineResponse)
async def online():
    """
    获取在线用户人员
    :return:
    """
    return {"msg": "获取成功",'code':2000,'data':list(active_connections.keys())}

# docker build --no-cache -t cy-chat:1.1 .  打包
# docker-compose up -d # 直接运行，需要有源码
## docker run -d --name cy-chat-app --env-file .env cy-chat:1.1 # 启动
## docker save -o cy-chat-1.1.tar cy-chat:1.1  保存到本地镜像
## docker load -i cy-chat-1.1.tar  本地docker加载
## docker run -d --name cy-chat-app --env-file .env cy-chat:1.1  最后在run启动
"""
.env
DATABASE_USER=chris
DATABASE_PASSWORD=qwe123
DATABASE_NAME=test
DATABASE_ENGINE=cy_query.db.backends.postgresql
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
SECRET_KEY=django-insecure-111111111
"""
